package com.sdkj.dspservice.modules.report.utils;

import com.alibaba.druid.util.StringUtils;
import com.sdkj.dspservice.common.constant.Consts;
import com.sdkj.dspservice.common.enums.DataTypeEnum;
import com.sdkj.dspservice.common.utils.MD5Utils;
import com.sdkj.dspservice.modules.report.utils.entity.JdbcDataSource;
import com.sdkj.dspservice.modules.report.utils.entity.JdbcSourceInfo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Matcher;

import static com.sdkj.dspservice.common.constant.Consts.*;

/**
 * 数据源工具类
 *
 * @author tm
 */
@Slf4j
@Data
public class SourceUtils {

    private JdbcDataSource jdbcDataSource;

    /**
     * 构造器
     *
     * @param jdbcDataSource
     */
    public SourceUtils(JdbcDataSource jdbcDataSource) {
        this.jdbcDataSource = jdbcDataSource;
    }

    /**
     * 测试数据源
     *
     * @param url      连接url
     * @param username 用户名
     * @param password 密码
     * @return
     */
    public static boolean testSource(String url, String username, String password) throws SQLException {
        Connection con = null;
        con = DriverManager.getConnection(url, username, password);
        return con != null;
    }

    /**
     * 获取数据库连接
     *
     * @param jdbcSourceInfo
     * @return
     * @throws SourceException
     */
    public Connection getConnection(JdbcSourceInfo jdbcSourceInfo) throws SourceException {
        // 获取连接
        Connection conn = getConnectionWithRetry(jdbcSourceInfo);
        if (conn == null) {
            try {
                // 去除无效连接
                jdbcDataSource.removeDatasource(jdbcSourceInfo);
                // 获取连接
                DataSource dataSource = jdbcDataSource.getDataSource(jdbcSourceInfo);
                return dataSource.getConnection();
            } catch (Exception e) {
                log.error("get connection error, jdbcUrl: {}", jdbcSourceInfo.getJdbcUrl());
                throw new SourceException("get connection error, jdbcUrl: " + jdbcSourceInfo.getJdbcUrl() + " you can try again later or reset datasource");
            }
        }
        return conn;
    }

    /**
     * 释放连接
     *
     * @param connection
     */
    public static void releaseConnection(Connection connection) {
        if (null != connection) {
            try {
                connection.close();
            } catch (Exception e) {
                log.error("connection release error", e.getMessage());
            }
        }
    }

    /**
     * 获取数据源名称（例：jdbc:mysql://localhost:3306/springboot_cms， 两个":"之间的字符串）
     *
     * @param jdbcUrl
     * @return
     */
    public static String getDataSourceName(String jdbcUrl) {
        String dataSourceName = null;
        jdbcUrl = jdbcUrl.replaceAll(NEW_LINE_CHAR, EMPTY).replaceAll(SPACE, EMPTY).trim().toLowerCase();
        Matcher matcher = PATTERN_JDBC_TYPE.matcher(jdbcUrl);
        if (matcher.find()) {
            dataSourceName = matcher.group().split(COLON)[1];
        }
        return dataSourceName;
    }

    /**
     * 获取驱动类名
     *
     * @param jdbcUrl
     * @param version
     * @return
     */
    public static String getDriverClassName(String jdbcUrl, String version) {

        String className = null;
        try {
            className = DriverManager.getDriver(jdbcUrl.trim()).getClass().getName();
        } catch (SQLException e) {

        }

        if (!StringUtils.isEmpty(className) && !className.contains("com.sun.proxy")
                && !className.contains("net.sf.cglib.proxy")) {
            return className;
        }

        DataTypeEnum dataTypeEnum = DataTypeEnum.urlOf(jdbcUrl);
        CustomDataSource customDataSource = null;
        if (null == dataTypeEnum) {
            try {
                customDataSource = CustomDataSourceUtils.getInstance(jdbcUrl, version);
            } catch (Exception e) {
                throw new SourceException(e.getMessage());
            }
        }

        if (null == dataTypeEnum && null == customDataSource) {
            throw new SourceException("Not supported data type: jdbcUrl=" + jdbcUrl);
        }

        return className = null != dataTypeEnum && !StringUtils.isEmpty(dataTypeEnum.getDriver())
                ? dataTypeEnum.getDriver()
                : customDataSource.getDriver().trim();
    }

    /**
     * 通过jdbc参数组装成一个唯一的key
     *
     * @param jdbcUrl
     * @param username
     * @param password
     * @param version
     * @param isExt
     * @return
     */
    public static String getKey(String jdbcUrl, String username, String password, String version, boolean isExt) {

        StringBuilder sb = new StringBuilder();

        if (!StringUtils.isEmpty(username)) {
            sb.append(username);
        }

        if (!StringUtils.isEmpty(password)) {
            sb.append(Consts.COLON).append(password);
        }

        sb.append(Consts.AT_SYMBOL).append(jdbcUrl.trim());

        if (isExt && !StringUtils.isEmpty(version)) {
            sb.append(Consts.COLON).append(version);
        }

        return MD5Utils.getMD5(sb.toString(), true, 64);
    }

    /**
     * 获取连接，如果获取失败，则循环获取3次
     *
     * @param jdbcSourceInfo
     * @return
     */
    private Connection getConnectionWithRetry(JdbcSourceInfo jdbcSourceInfo) {
        int rc = 1;
        for (; ; ) {
            if (rc > 3) {
                return null;
            }
            Connection connection;
            try {
                connection = jdbcDataSource.getDataSource(jdbcSourceInfo).getConnection();
                if (connection != null && connection.isValid(5)) {
                    return connection;
                }
                Thread.sleep((long) Math.pow(2, rc) * 1000);
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            rc++;
        }
    }

    public static void closeResult(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                log.error("resultSet close error", e.getMessage());
            }
        }
    }
}
